/*
 * File    : OverallStatsImpl.java
 * Created : 2 mars 2004
 * By      : Olivier
 *
 * Azureus - a Java Bittorrent client
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details ( see the LICENSE file ).
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package org.gudy.azureus2.core3.stats.transfer.impl;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.gudy.azureus2.core3.global.GlobalManager;
import org.gudy.azureus2.core3.global.GlobalManagerAdapter;
import org.gudy.azureus2.core3.global.GlobalManagerStats;
import org.gudy.azureus2.core3.stats.transfer.OverallStats;
import org.gudy.azureus2.core3.util.AEMonitor;
import org.gudy.azureus2.core3.util.FileUtil;
import org.gudy.azureus2.core3.util.SimpleTimer;
import org.gudy.azureus2.core3.util.SystemTime;
import org.gudy.azureus2.core3.util.TimerEvent;
import org.gudy.azureus2.core3.util.TimerEventPerformer;
import org.gudy.azureus2.plugins.PluginInterface;
import org.gudy.azureus2.plugins.PluginManager;

import com.aelitis.azureus.core.AzureusCore;
import com.aelitis.azureus.core.AzureusCoreComponent;
import com.aelitis.azureus.core.AzureusCoreLifecycleAdapter;
import com.aelitis.azureus.core.dht.DHT;
import com.aelitis.azureus.core.dht.transport.DHTTransportStats;
import com.aelitis.azureus.core.stats.AzureusCoreStats;
import com.aelitis.azureus.core.stats.AzureusCoreStatsProvider;
import com.aelitis.azureus.plugins.dht.DHTPlugin;

/**
 * @author Olivier
 * 
 */
public class OverallStatsImpl extends GlobalManagerAdapter implements OverallStats {

    // sizes in MB

    private static final long TEN_YEARS = 60 * 60 * 24 * 365 * 10L;

    private static final int STATS_PERIOD = 60 * 1000; // 1 min
    private static final int SAVE_PERIOD = 10 * 60 * 1000; // 10 min
    private static final int SAVE_TICKS = SAVE_PERIOD / STATS_PERIOD;

    private AzureusCore core;
    private GlobalManagerStats gm_stats;

    private DHT[] dhts;

    private long totalDownloaded;
    private long totalUploaded;
    private long totalUptime;

    private long markTime;
    private long markTotalDownloaded;
    private long markTotalUploaded;
    private long markTotalUptime;

    private long totalDHTUploaded;
    private long totalDHTDownloaded;

    private long lastDownloaded;
    private long lastUploaded;
    private long lastUptime;

    private long lastDHTUploaded;
    private long lastDHTDownloaded;

    // separate stats

    private long totalProtocolUploaded;
    private long totalDataUploaded;
    private long totalProtocolDownloaded;
    private long totalDataDownloaded;

    private long lastProtocolUploaded;
    private long lastDataUploaded;
    private long lastProtocolDownloaded;
    private long lastDataDownloaded;

    private long[] lastSnapshot;

    private long session_start_time = SystemTime.getCurrentTime();

    protected AEMonitor this_mon = new AEMonitor("OverallStats");

    private int tick_count;

    private Map load(String filename) {
        return (FileUtil.readResilientConfigFile(filename));
    }

    private Map load() {
        return (load("azureus.statistics"));
    }

    private void save(String filename, Map map) {
        try {
            this_mon.enter();

            FileUtil.writeResilientConfigFile(filename, map);

        } finally {

            this_mon.exit();
        }
    }

    private void save(Map map) {
        save("azureus.statistics", map);
    }

    private void validateAndLoadValues(Map statisticsMap) {

        lastUptime = SystemTime.getCurrentTime() / 1000;

        Map overallMap = (Map) statisticsMap.get("all");

        totalDownloaded = getLong(overallMap, "downloaded");
        totalUploaded = getLong(overallMap, "uploaded");
        totalUptime = getLong(overallMap, "uptime");

        markTime = getLong(overallMap, "mark_time");
        markTotalDownloaded = getLong(overallMap, "mark_downloaded");
        markTotalUploaded = getLong(overallMap, "mark_uploaded");
        markTotalUptime = getLong(overallMap, "mark_uptime");

        totalDHTDownloaded = getLong(overallMap, "dht_down");
        totalDHTUploaded = getLong(overallMap, "dht_up");

        totalProtocolUploaded = getLong(overallMap, "p_uploaded");
        totalDataUploaded = getLong(overallMap, "d_uploaded");
        totalProtocolDownloaded = getLong(overallMap, "p_downloaded");
        totalDataDownloaded = getLong(overallMap, "d_downloaded");

        long current_total_d_received = gm_stats.getTotalDataBytesReceived();
        long current_total_p_received = gm_stats.getTotalProtocolBytesReceived();

        long current_total_d_sent = gm_stats.getTotalDataBytesSent();
        long current_total_p_sent = gm_stats.getTotalProtocolBytesSent();

        lastSnapshot =
                new long[] { totalProtocolUploaded, totalDataUploaded, totalProtocolDownloaded, totalDataDownloaded, totalDHTUploaded,
                        totalDHTDownloaded, current_total_p_sent, current_total_d_sent, current_total_p_received, current_total_d_received, 0, 0 };
    }

    protected long getLong(Map map, String name) {
        if (map == null) {
            return (0);
        }

        Object obj = map.get(name);

        if (!(obj instanceof Long)) {
            return (0);
        }

        return (((Long) obj).longValue());
    }

    public OverallStatsImpl(AzureusCore _core, GlobalManagerStats _gm_stats) {
        core = _core;
        gm_stats = _gm_stats;

        Map stats = load();

        validateAndLoadValues(stats);

        Set types = new HashSet();

        types.add(AzureusCoreStats.ST_XFER_UPLOADED_PROTOCOL_BYTES);
        types.add(AzureusCoreStats.ST_XFER_UPLOADED_DATA_BYTES);
        types.add(AzureusCoreStats.ST_XFER_DOWNLOADED_PROTOCOL_BYTES);
        types.add(AzureusCoreStats.ST_XFER_DOWNLOADED_DATA_BYTES);

        AzureusCoreStats.registerProvider(types, new AzureusCoreStatsProvider() {
            public void updateStats(Set types, Map values) {
                try {
                    this_mon.enter();

                    if (core.isStarted()) {

                        if (types.contains(AzureusCoreStats.ST_XFER_UPLOADED_PROTOCOL_BYTES)) {

                            values.put(AzureusCoreStats.ST_XFER_UPLOADED_PROTOCOL_BYTES, new Long(totalProtocolUploaded
                                    + (gm_stats.getTotalProtocolBytesSent() - lastProtocolUploaded)));
                        }
                        if (types.contains(AzureusCoreStats.ST_XFER_UPLOADED_DATA_BYTES)) {

                            values.put(AzureusCoreStats.ST_XFER_UPLOADED_DATA_BYTES, new Long(totalDataUploaded
                                    + (gm_stats.getTotalDataBytesSent() - lastDataUploaded)));
                        }
                        if (types.contains(AzureusCoreStats.ST_XFER_DOWNLOADED_PROTOCOL_BYTES)) {

                            values.put(AzureusCoreStats.ST_XFER_DOWNLOADED_PROTOCOL_BYTES, new Long(totalProtocolDownloaded
                                    + (gm_stats.getTotalProtocolBytesReceived() - lastProtocolDownloaded)));
                        }
                        if (types.contains(AzureusCoreStats.ST_XFER_DOWNLOADED_DATA_BYTES)) {

                            values.put(AzureusCoreStats.ST_XFER_DOWNLOADED_DATA_BYTES, new Long(totalDataDownloaded
                                    + (gm_stats.getTotalDataBytesReceived() - lastDataDownloaded)));
                        }
                    }
                } finally {

                    this_mon.exit();
                }
            }
        });

        core.addLifecycleListener(new AzureusCoreLifecycleAdapter() {
            public void componentCreated(AzureusCore core, AzureusCoreComponent component) {
                if (component instanceof GlobalManager) {

                    GlobalManager gm = (GlobalManager) component;

                    gm.addListener(OverallStatsImpl.this, false);

                    SimpleTimer.addPeriodicEvent("OverallStats", STATS_PERIOD, new TimerEventPerformer() {
                        public void perform(TimerEvent event) {
                            updateStats(false);
                        }
                    });
                }
            }
        });

    }

    public int getAverageDownloadSpeed() {
        if (totalUptime > 1) {
            return (int) (totalDownloaded / totalUptime);
        }
        return 0;
    }

    public int getAverageUploadSpeed() {
        if (totalUptime > 1) {
            return (int) (totalUploaded / totalUptime);
        }
        return 0;
    }

    public long getDownloadedBytes() {
        return totalDownloaded;
    }

    public long getUploadedBytes() {
        return totalUploaded;
    }

    public long getTotalUpTime() {
        return totalUptime;
    }

    public long getDownloadedBytes(boolean since_mark) {
        if (since_mark) {
            if (markTotalDownloaded > totalDownloaded) {
                markTotalDownloaded = totalDownloaded;
            }
            return (totalDownloaded - markTotalDownloaded);
        } else {
            return (totalDownloaded);
        }
    }

    public long getUploadedBytes(boolean since_mark) {
        if (since_mark) {
            if (markTotalUploaded > totalUploaded) {
                markTotalUploaded = totalUploaded;
            }
            return (totalUploaded - markTotalUploaded);
        } else {
            return (totalUploaded);
        }
    }

    public long getTotalUpTime(boolean since_mark) {
        if (since_mark) {
            if (markTotalUptime > totalUptime) {
                markTotalUptime = totalUptime;
            }
            return (totalUptime - markTotalUptime);
        } else {
            return (totalUptime);
        }
    }

    public int getAverageDownloadSpeed(boolean since_mark) {
        if (since_mark) {
            long up_time = getTotalUpTime(true);
            long down = getDownloadedBytes(true);
            if (up_time > 1) {
                return (int) (down / up_time);
            }
            return 0;
        } else {
            return (getAverageDownloadSpeed());
        }
    }

    public int getAverageUploadSpeed(boolean since_mark) {
        if (since_mark) {
            long up_time = getTotalUpTime(true);
            long up = getUploadedBytes(true);
            if (up_time > 1) {
                return (int) (up / up_time);
            }
            return 0;
        } else {
            return (getAverageUploadSpeed());
        }
    }

    public long getMarkTime() {
        return (markTime);
    }

    public void setMark() {
        markTime = SystemTime.getCurrentTime();
        markTotalDownloaded = totalDownloaded;
        markTotalUploaded = totalUploaded;
        markTotalUptime = totalUptime;
    }

    public void clearMark() {
        markTime = 0;
        markTotalDownloaded = 0;
        markTotalUploaded = 0;
        markTotalUptime = 0;
    }

    public long getSessionUpTime() {
        return (SystemTime.getCurrentTime() - session_start_time) / 1000;
    }

    public void destroyInitiated() {
        updateStats(true);
    }

    protected long[] getLastSnapshot() {
        try {
            this_mon.enter();

            return (lastSnapshot);

        } finally {

            this_mon.exit();
        }
    }

    private void updateStats(boolean force) {
        try {
            this_mon.enter();

            long current_time = SystemTime.getCurrentTime() / 1000;

            if (current_time < lastUptime) { // time went backwards
                lastUptime = current_time;
                return;
            }

            long current_total_d_received = gm_stats.getTotalDataBytesReceived();
            long current_total_p_received = gm_stats.getTotalProtocolBytesReceived();

            long current_total_d_sent = gm_stats.getTotalDataBytesSent();
            long current_total_p_sent = gm_stats.getTotalProtocolBytesSent();

            long current_total_received = current_total_d_received + current_total_p_received;
            long current_total_sent = current_total_d_sent + current_total_p_sent;

            // overall totals

            totalDownloaded += current_total_received - lastDownloaded;
            lastDownloaded = current_total_received;
            if (totalDownloaded < 0)
                totalDownloaded = 0;

            totalUploaded += current_total_sent - lastUploaded;
            lastUploaded = current_total_sent;
            if (totalUploaded < 0)
                totalUploaded = 0;

            // split totals

            totalDataDownloaded += current_total_d_received - lastDataDownloaded;
            lastDataDownloaded = current_total_d_received;
            if (totalDataDownloaded < 0)
                totalDataDownloaded = 0;

            totalProtocolDownloaded += current_total_p_received - lastProtocolDownloaded;
            lastProtocolDownloaded = current_total_p_received;
            if (totalProtocolDownloaded < 0)
                totalProtocolDownloaded = 0;

            totalDataUploaded += current_total_d_sent - lastDataUploaded;
            lastDataUploaded = current_total_d_sent;
            if (totalDataUploaded < 0)
                totalDataUploaded = 0;

            totalProtocolUploaded += current_total_p_sent - lastProtocolUploaded;
            lastProtocolUploaded = current_total_p_sent;
            if (totalProtocolUploaded < 0)
                totalProtocolUploaded = 0;

            // DHT

            if (dhts == null) {

                try {
                    PluginManager pm = core.getPluginManager();

                    if (pm.isInitialized()) {

                        PluginInterface dht_pi = pm.getPluginInterfaceByClass(DHTPlugin.class);

                        if (dht_pi == null) {

                            dhts = new DHT[0];

                        } else {

                            DHTPlugin plugin = (DHTPlugin) dht_pi.getPlugin();

                            if (!plugin.isInitialising()) {

                                if (plugin.isEnabled()) {

                                    dhts = ((DHTPlugin) dht_pi.getPlugin()).getDHTs();

                                } else {

                                    dhts = new DHT[0];
                                }
                            }
                        }
                    }
                } catch (Throwable e) {

                    dhts = new DHT[0];
                }
            }

            long current_total_dht_up = 0;
            long current_total_dht_down = 0;

            if (dhts != null) {

                for (DHT dht : dhts) {

                    DHTTransportStats stats = dht.getTransport().getStats();

                    current_total_dht_up += stats.getBytesSent();
                    current_total_dht_down += stats.getBytesReceived();
                }
            }

            totalDHTUploaded += current_total_dht_up - lastDHTUploaded;
            lastDHTUploaded = current_total_dht_up;
            if (totalDHTUploaded < 0)
                totalDHTUploaded = 0;

            totalDHTDownloaded += current_total_dht_down - lastDHTDownloaded;
            lastDHTDownloaded = current_total_dht_down;
            if (totalDHTDownloaded < 0)
                totalDHTDownloaded = 0;

            // TIME

            long delta = current_time - lastUptime;

            if (delta > 100 || delta < 0) { // make sure the time diff isn't borked
                lastUptime = current_time;
                return;
            }

            if (totalUptime > TEN_YEARS) { // total uptime > 10years is an error, reset
                totalUptime = 0;
            }

            if (totalUptime < 0)
                totalUptime = 0;

            totalUptime += delta;
            lastUptime = current_time;

            lastSnapshot =
                    new long[] { totalProtocolUploaded, totalDataUploaded, totalProtocolDownloaded, totalDataDownloaded, totalDHTUploaded,
                            totalDHTDownloaded, current_total_p_sent, current_total_d_sent, current_total_p_received, current_total_d_received,
                            current_total_dht_up, current_total_dht_down };

            HashMap overallMap = new HashMap();

            overallMap.put("downloaded", new Long(totalDownloaded));
            overallMap.put("uploaded", new Long(totalUploaded));
            overallMap.put("uptime", new Long(totalUptime));

            overallMap.put("mark_time", new Long(markTime));
            overallMap.put("mark_downloaded", new Long(markTotalDownloaded));
            overallMap.put("mark_uploaded", new Long(markTotalUploaded));
            overallMap.put("mark_uptime", new Long(markTotalUptime));

            overallMap.put("dht_down", new Long(totalDHTDownloaded));
            overallMap.put("dht_up", new Long(totalDHTUploaded));

            overallMap.put("p_uploaded", new Long(totalProtocolUploaded));
            overallMap.put("d_uploaded", new Long(totalDataUploaded));
            overallMap.put("p_downloaded", new Long(totalProtocolDownloaded));
            overallMap.put("d_downloaded", new Long(totalDataDownloaded));

            Map map = new HashMap();

            map.put("all", overallMap);

            tick_count++;

            if (force || tick_count % SAVE_TICKS == 0) {

                save(map);
            }
        } finally {

            this_mon.exit();
        }
    }
}
